﻿using System;
using System.Collections.Generic;
using System.Xml.Serialization;
using vJine.Core.IO.Json;

namespace vJine.Core.Task {
    public partial class Cron : IComparer<Cron.Job> {
        /// <summary>
        /// 任务类型
        /// </summary>
        [Serializable]
        public partial class Job {
            /// <summary>
            /// 实例化任务类型
            /// </summary>
            public Job() {
                this.Init = true;
            }
            /// <summary>
            /// 任务名称
            /// </summary>
            [XmlAttribute("Name")]
            public string Name { get; set; }
            /// <summary>
            /// 是否执行At指定的时间点
            /// </summary>
            [XmlAttribute("Init")]
            public bool Init { get; set; }
            /// <summary>
            /// 是否忽略过期的任务
            /// </summary>
            [XmlAttribute("Ignore")]
            public bool Ignore { get; set; }
            /// <summary>
            /// 任务间隔（W：周，D：日，H：小时，M：分钟，S：秒，m：毫秒）
            /// </summary>
            [XmlAttribute("Period")]
            public string Period { get; set; }
            /// <summary>
            /// 任务开始时间
            /// </summary>
            [XmlElement("At")]
            public DateTime At { get; set; }
            /// <summary>
            /// 任务终止时间
            /// </summary>
            [XmlAttribute("End")]
            public DateTime? End { get; set; } //最终运行时间
            /// <summary>
            /// 最多运行次数
            /// </summary>
            [XmlAttribute("MaxTimes")]
            public int? MaxTimes { get; set; } //最多运行次数
            /// <summary>
            /// 任务备注
            /// </summary>
            [XmlElement("Comments")]
            public string Comments { get; set; }

            public override string ToString() {
                return
                    JsonHelper.ToString<Job>(this);
            }
        }

        public partial class Job {
            internal DateTime? Next { get; set; }
            internal long get_duetime() {
                return this.get_duetime(DateTime.Now);
            }

            internal long get_duetime(DateTime now) {
                if (this.Next == null) {
                    return -1;
                }

                lock (this) {
                    long duetime =
                        (long)(this.Next.Value - now).TotalMilliseconds;

                    return duetime >= 0 ? duetime : 0;
                }
            }

            internal int times = 0;
            internal bool IsIgnored = false;
            internal DateTime? SetNext(DateTime now) {
                if (this.At == null) {
                    this.At = now;
                }

                if (this.Next == null) {
                    if (this.times > 0) {
                        return null;
                    } else {
                        this.Next = this.At;
                    }
                }

                if (!string.IsNullOrEmpty(this.Period)) {
                    this.getNextPoint(now);
                }

                if (this.End != null && this.Next > this.End) {
                    this.Next = null; return null;
                }
                if (this.MaxTimes > 0 && ++this.times > this.MaxTimes) {
                    this.Next = null; return null;
                }

                return this.Next;
            }

            void getNextPoint(DateTime now) {
                string symbol = this.Period.Substring(0, 1).ToUpper();
                double delay = double.Parse(this.Period.Substring(1));
                switch (symbol) {
                    case "W"://week
                        delay *= 7;
                        if (this.Ignore && !this.IsIgnored && this.Next < now && !(this.Init && this.times == 0)) {
                            int over_times =
                                (int)Math.Ceiling((now - this.Next.Value).TotalDays / delay);
                            this.Next =
                                this.Next.Value.AddDays(over_times * delay);

                            this.IsIgnored = true;
                        } else {
                            this.Next =
                                this.Next.Value.AddDays(delay);
                        }
                        break;
                    case "D"://day
                        if (this.Ignore && !this.IsIgnored && this.Next < now && !(this.Init && this.times == 0)) {
                            int over_times =
                                (int)Math.Ceiling((now - this.Next.Value).TotalDays / delay);
                            this.Next =
                                this.Next.Value.AddDays(over_times * delay);

                            this.IsIgnored = true;
                        } else {
                            this.Next =
                                this.Next.Value.AddDays(delay);
                        }
                        break;
                    case "H"://hour
                        if (this.Ignore && !this.IsIgnored && this.Next < now && !(this.Init && this.times == 0)) {
                            int over_times =
                                (int)Math.Ceiling((now - this.Next.Value).TotalHours / delay);
                            this.Next =
                                this.Next.Value.AddHours(over_times * delay);

                            this.IsIgnored = true;
                        } else {
                            this.Next =
                                this.Next.Value.AddHours(delay);
                        }
                        break;
                    case "M"://minute
                        if (this.Ignore && !this.IsIgnored && this.Next < now && !(this.Init && this.times == 0)) {
                            int over_times =
                                (int)Math.Ceiling((now - this.Next.Value).TotalMinutes / delay);
                            this.Next =
                                this.Next.Value.AddMinutes(over_times * delay);

                            this.IsIgnored = true;
                        } else {
                            this.Next =
                                this.Next.Value.AddMinutes(delay);
                        }
                        break;
                    case "S"://second
                        if (this.Ignore && !this.IsIgnored && this.Next < now && !(this.Init && this.times == 0)) {
                            int over_times =
                                (int)Math.Ceiling((now - this.Next.Value).TotalSeconds / delay);
                            this.Next =
                                this.Next.Value.AddSeconds(over_times * delay);

                            this.IsIgnored = true;
                        } else {
                            this.Next =
                                this.Next.Value.AddSeconds(delay);
                        }
                        break;
                    case "m"://ms
                        if (this.Ignore && !this.IsIgnored && this.Next < now && !(this.Init && this.times == 0)) {
                            int over_times =
                                (int)Math.Ceiling((now - this.Next.Value).TotalMilliseconds / delay);
                            this.Next =
                                this.Next.Value.AddMilliseconds(over_times * delay);

                            this.IsIgnored = true;
                        } else {
                            this.Next =
                                this.Next.Value.AddMilliseconds(delay);
                        }
                        break;
                    default:
                        this.Next = null;
                        break;
                }
            }
        }
    }
}
